﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace AllenalaMente.Games.Bubbles
{
    public partial class Bubbles : UserControl, GameInterface
    {
        private List<Bubble> bubbles;
        private DispatcherTimer updateTimer, startTimer;
        private TimeSpan startTime;
        private Random random;
        private Rect gameBounds;
        private List<int> extractedValues;
        private int score, numBubbles, numGuessedPartial, numGuessedTotal, numWrongTotal;
        private UITimer UIgameTimer;
        private UIScore UIgameScore;
        private List<Storyboard> enterAnimations;
        private bool letters;

        public Bubbles()
        {
            InitializeComponent();

            // Initalize game
            random = new Random();
            gameBounds = new Rect(0, 0, LayoutRoot.Width, LayoutRoot.Height);
            score = 0;
            numBubbles = 3;
            numGuessedPartial = 0;
            numGuessedTotal = 0;
            numWrongTotal = 0;
            letters = false;

            // Initialize bubbles
            bubbles = new List<Bubble>();
            extractedValues = new List<int>();
            enterAnimations = new List<Storyboard>();
            //GenerateBubbles();

            // Start timer
            startTimer = new DispatcherTimer();
            startTimer.Interval = TimeSpan.FromMilliseconds(600);
            startTimer.Tick += new EventHandler(startTimer_Tick);
            startTimer.Start();
            startTime = TimeSpan.FromSeconds(3);
            TextBlockReady.Text = Locale.Strings.Ready;

            // Update timer
            updateTimer = new DispatcherTimer();
            updateTimer.Interval = TimeSpan.FromMilliseconds(15);
            updateTimer.Tick += new EventHandler(updateTimer_Tick);
            //updateTimer.Start();

            // UI Game timer
            UIgameTimer = new UITimer(60);
            LayoutRoot.Children.Add(UIgameTimer);
            UIgameTimer.timeOut += new EventHandler(game_TimeOut);
            //UIgameTimer.Start();

            // UI Game Score
            UIgameScore = new UIScore();
            LayoutRoot.Children.Add(UIgameScore);
            Canvas.SetLeft(UIgameScore, 750);
        }


        #region Metodi

        private void GenerateBubbles()
        {
            // Ripulisco le liste ove necessario
            if (bubbles.Count > 0)
            {
                for (int i = 0; i < bubbles.Count; i++)
                {
                    bubbles[i].MouseLeftButtonDown -= bubble_MouseLeftButtonDown;
                    LayoutRoot.Children.Remove(bubbles[i]);
                }

                bubbles.Clear();
                extractedValues.Clear();
            }

            // Ricreo le sfere
            int maxValue = numBubbles * 3;
            for (int i = 0; i < numBubbles; i++)
            {
                int bubbleValue = 0;
                if (letters)
                {
                    bubbleValue = random.Next(65, 90);
                    while (extractedValues.Contains(bubbleValue))
                        bubbleValue = random.Next(65, 90);
                }
                else
                {
                    bubbleValue = random.Next(-maxValue, maxValue);
                    while (extractedValues.Contains(bubbleValue))
                        bubbleValue = random.Next(-maxValue, maxValue);
                }

                extractedValues.Add(bubbleValue);
                BubbleColor color = (BubbleColor)random.Next((int)BubbleColor.Blue, (int)BubbleColor.TotalColors);

                Point speed = new Point(random.Next(4, 10) * 0.1, random.Next(4, 10) * 0.1);
                speed.X = random.Next() % 2 == 0 ? speed.X * -1 : speed.X * 1;
                speed.Y = random.Next() % 2 == 0 ? speed.Y * -1 : speed.Y * 1;

                Bubble currentBubble = new Bubble(color, bubbleValue, random.Next(50, 70), speed, letters);
                LayoutRoot.Children.Add(currentBubble);
                currentBubble.transformBubble.TranslateX = 80 + (i * (gameBounds.Width / numBubbles));
                currentBubble.transformBubble.TranslateY = 500 + (100 * random.Next(3));

                bubbles.Add(currentBubble);

                // Lancio animazione ingresso bolle dal basso
                EnterBubbles(currentBubble);
            }
        }

        private void ActivateInput()
        {
            for (int i = 0; i < bubbles.Count; i++)
            {
                // Register pop event
                bubbles[i].MouseLeftButtonDown += new MouseButtonEventHandler(bubble_MouseLeftButtonDown);
            }
        }

        private bool IsMinValue(int value)
        {
            bool valueIsMin = true;
            for (int i = 0; i < extractedValues.Count; i++)
            {
                if (Math.Min(extractedValues[i], value) != value)
                    valueIsMin = false;
            }
            return valueIsMin;
        }

        private void IncreaseScore(int amount)
        {
            score += amount;
            UIgameScore.SetScore(score);
        }


        #endregion


        #region Animazioni

        public void EnterBubbles(Bubble bubble)
        {
            // Plays an animation that moves a bubble from the bottom to its final position
            DoubleAnimation translateYAnimation = new DoubleAnimation();
            double currentY = bubble.transformBubble.TranslateY;
            translateYAnimation.From = currentY;
            translateYAnimation.To = currentY - 400;
            translateYAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.5));

            Storyboard.SetTarget(translateYAnimation, bubble.transformBubble);
            Storyboard.SetTargetProperty(translateYAnimation, new PropertyPath("TranslateY"));

            Storyboard sb = new Storyboard();
            sb.Children.Add(translateYAnimation);
            enterAnimations.Add(sb);
            sb.Completed += new EventHandler(bubblesEnter_Completed);
            sb.Begin();
        }

        #endregion


        #region Eventi

        public event GameEvent GameEnd;

        private void startTimer_Tick(object sender, EventArgs e)
        {
            startTime = startTime.Subtract(TimeSpan.FromSeconds(1));
            if (startTime.Seconds < 2)
                TextBlockReady.Text = Locale.Strings.GO;
            if (startTime.Seconds < 1)
            {
                // The countdown is finished, the game can start
                startTimer.Tick -= startTimer_Tick;
                CanvasCountdown.Visibility = Visibility.Collapsed;
                updateTimer.Start();
                UIgameTimer.Start();
                GenerateBubbles();
            }
        }

        private void updateTimer_Tick(object sender, EventArgs e)
        {
            for (int i = 0; i < bubbles.Count; i++)
            {
                bubbles[i].UpdatePosition();
                bubbles[i].collisionResolved = false;
            }

            for (int i = 0; i < bubbles.Count; i++)
            {
                bool leftRightHit = false;
                bool upDownHit = false;
                // Check collisions with borders
                // Left and right borders
                if (bubbles[i].transformBubble.TranslateX - bubbles[i].radius < gameBounds.X || bubbles[i].transformBubble.TranslateX + bubbles[i].radius > gameBounds.Width)
                    leftRightHit = true;
                // Up and down
                if (bubbles[i].transformBubble.TranslateY - bubbles[i].radius < gameBounds.Y || bubbles[i].transformBubble.TranslateY + bubbles[i].radius > gameBounds.Height)
                    upDownHit = true;

                if (leftRightHit || upDownHit)
                {
                    bubbles[i].Rebound(leftRightHit, upDownHit);
                    continue;
                }

                if (bubbles[i].collisionResolved)
                    continue;

                // Check collisions between bubbles
                Bubble bubbleOne = bubbles[i];
                for (int j = 0; j < bubbles.Count; j++)
                {
                    if (i == j)
                        continue;
                    Bubble bubbleTwo = bubbles[j];
                    int bubbleMinDistance = bubbleOne.radius + bubbleTwo.radius;
                    Point distanceVector = new Point(bubbleOne.transformBubble.TranslateX - bubbleTwo.transformBubble.TranslateX, bubbleOne.transformBubble.TranslateY - bubbleTwo.transformBubble.TranslateY);
                    double bubbleDistance = Math.Sqrt(Math.Pow(distanceVector.X, 2) + Math.Pow(distanceVector.Y, 2));
                    if (bubbleDistance < bubbleMinDistance)
                    {
                        // The two bubbles collided
                        Point speedOne = bubbleOne.speed;
                        bubbleOne.speed = bubbleTwo.speed;
                        bubbleTwo.speed = speedOne;
                        bubbleOne.collisionResolved = true;
                        bubbleTwo.collisionResolved = true;
                        break;
                    }
                }
            }
        }


        private void bubble_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // A bubble was hit
            Bubble hitBubble = (Bubble)sender;
            hitBubble.MouseLeftButtonDown -= bubble_MouseLeftButtonDown;
            if (IsMinValue(hitBubble.value))
            {
                // Remove the bubble and increase the score
                extractedValues.Remove(hitBubble.value);
                bubbles.Remove(hitBubble);
                hitBubble.BubblePopped += new EventHandler(bubble_Popped);
                hitBubble.Pop();

                //IncreaseScore(5);

                if (bubbles.Count == 0)
                {
                    // If it was the last bubble, assign points and generate next sequence
                    IncreaseScore(100 + (numGuessedTotal * 10));

                    numGuessedPartial++;
                    numGuessedTotal++;
                    if (numGuessedPartial % 2 == 0)
                        numBubbles++;

                    // Change to letters or viceversa
                    if (letters)
                        letters = false;
                    if ((numGuessedTotal + numWrongTotal) % 3 == 0)
                        letters = true;

                    GenerateBubbles();
                }
            }
            else
            {
                // The user hit the wrong bubble: generate next sequence
                numGuessedPartial = 0;
                numWrongTotal++;

                if (letters)
                    letters = false;

                GenerateBubbles();
            }
        }

        private void bubble_Popped(object sender, EventArgs e)
        {
            Bubble poppedBubble = (Bubble)sender;
            poppedBubble.BubblePopped -= bubble_Popped;
            LayoutRoot.Children.Remove(poppedBubble);
            poppedBubble = null;
        }

        private void bubblesEnter_Completed(object sender, EventArgs e)
        {
            Storyboard sb = (Storyboard)sender;
            sb.Completed -= bubblesEnter_Completed;
            enterAnimations.Remove(sb);
            if (enterAnimations.Count == 0)
                ActivateInput();
            sb = null;
        }

        private void game_TimeOut(object sender, EventArgs e)
        {
            // Finito il tempo
            GameEventArgs gea = new GameEventArgs();
            gea.reason = GameCloseReason.Timeout;
            gea.score = this.score;
            gea.giuste = numGuessedTotal;
            gea.sbagliate = numWrongTotal;
            if (GameEnd != null)
                GameEnd(this, gea);
        }

        #endregion
    }
}
